﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Xml;
using log4net;

namespace Possan.IOC
{
	public class TypeRegistry
	{
		private static ILog logger = LogManager.GetLogger(typeof(TypeRegistry));
		
		private List<Type> activator_attributes;
		private Dictionary<Type, BaseTypeRecord> interfaces;

		public TypeRegistry()
		{
			Reset();
		}

		public void Reset()
		{
			activator_attributes = new List<Type>();
			interfaces = new Dictionary<Type, BaseTypeRecord>();
			logger.Debug("Resetting registry");
		}

		public void AddActivatorAttribute(Type type)
		{
			activator_attributes.Add(type);
		}

		public int NumberOfTypes
		{
			get
			{
				return interfaces.Count;
			}
		}

		public object Construct(Type t)
		{
			object o = null;
			if (t != null)
			{
				ConstructorInfo[] cis = t.GetConstructors();

				if (cis != null && cis.Length > 0)
				{

					int most_matches = -1;
					ConstructorInfo most_matches_ctor = null;
					foreach (ConstructorInfo ci in cis)
					{
						int matches = 0;
						ParameterInfo[] pis = ci.GetParameters();
						foreach (ParameterInfo pi in pis)
						{
							if (interfaces.ContainsKey(pi.ParameterType))
							{
								matches++;
							}
						}

						if (matches > 0 && matches > most_matches)
						{
							most_matches = matches;
							most_matches_ctor = ci;
						}
					}

					if (most_matches_ctor != null)
					{
						ParameterInfo[] pis = most_matches_ctor.GetParameters();

						object[] args = new object[pis.Length];
						for (int j = 0; j < pis.Length; j++)
						{
							ParameterInfo pi = pis[j];
							if (interfaces.ContainsKey(pi.ParameterType) && pi.ParameterType != t)
							{
								args[j] = Create(pi.ParameterType);
							}
							else
							{
								args[j] = null;
							}
						}

						o = Activator.CreateInstance(t, args, null);
					}
					else
					{
						// use default constructor
						try
						{
							o = Activator.CreateInstance(t);
						}
						catch (Exception z)
						{
							logger.Error("Failed to create an " + t + " without constructor parameters", z);
						}
					}
				}
				else
				{
					try
					{
						o = Activator.CreateInstance(t);
					}
					catch (Exception z)
					{
						logger.Error("Failed to create an " + t + " without constructor parameters", z);
					}
				}
			}

			Inject(o);

			return o;
		}

		public T Create<T>()
		{
			Type interfacetype = typeof(T);

			if (interfaces.ContainsKey(interfacetype))
				return interfaces[interfacetype].Create<T>(this);

			return default(T);
		}

		public T Create<T>(Type t)
		{
			//	Logger.Debug(typeof(TypeRegistry), "Creating instance of type " + interfacetype);
			try
			{
				return (T)Construct(t);
			}
			catch (Exception z)
			{
				logger.Error("Failed to create " + t, z);
			}
			
			return default(T);
		}

		public object Create(Type interfacetype)
		{
			//	Logger.Debug(typeof(TypeRegistry), "Creating instance of type " + interfacetype);
			
			if (interfaces.ContainsKey(interfacetype))
				return interfaces[interfacetype].Create(this);

			return Construct(interfacetype);
			
			// return null;
		}

		[Obsolete("Get is obsolete, use Create() instead", false)]
		public object Get(Type t)
		{
			return Create(t);
		}

		[Obsolete("Get<T> is obsolete, use Create<T>() instead.", false)]
		public T Get<T>()
		{
			return Create<T>();
		}

		public List<Type> GetAllTypes<T>()
		{
			Type interfacetype = typeof(T);
			List<Type> ret = new List<Type>();

			if (interfaces.ContainsKey(interfacetype))
				ret = interfaces[interfacetype].GetAllTypes();
			
			return ret;
		}

		public List<Type> GetAllTypes(Type interfacetype)
		{
			List<Type> ret = new List<Type>();

			if (interfaces.ContainsKey(interfacetype))
				ret = interfaces[interfacetype].GetAllTypes();
			
			return ret;
		}


		public List<object> GetAll(Type t)
		{
			List<object> ret = new List<object>();
			if (interfaces.ContainsKey(t))
				ret = interfaces[t].GetAll(this);

			return ret;
		}

		public List<T> GetAll<T>()
		{
			Type interfacetype = typeof(T);

			List<T> ret = new List<T>();
			if (interfaces.ContainsKey(interfacetype))
				ret = interfaces[interfacetype].GetAll<T>(this);

			return ret;
		}

		public void Register(Type interfacetype, Type realtype, ObjectScope scope)
		{
			logger.Debug("Registering type " + realtype + " for " + interfacetype + " with scope: " + scope.ToString());

			if (!interfaces.ContainsKey(interfacetype))
				interfaces.Add(interfacetype, new BaseTypeRecord());

			interfaces[interfacetype].RegisterImplementor(realtype, scope);
		}

		public void Register<T>(Type realtype, ObjectScope scope)
		{
			Type interfacetype = typeof(T);

			logger.Debug("Registering type " + realtype + " for " + interfacetype);

			if (!interfaces.ContainsKey(interfacetype))
				interfaces.Add(interfacetype, new BaseTypeRecord());

			interfaces[interfacetype].RegisterImplementor(realtype, scope);
		}

		public void RegisterSingleton(Type interfacetype, object instance)
		{
			Register(interfacetype, instance.GetType(), ObjectScope.SharedGlobal);
		}

		public void RegisterSingleton<T>(T instance)
		{
			Register(typeof(T), instance.GetType(), ObjectScope.SharedGlobal);
		}

		public void RegisterAllClasses(ITypeRegistryLoader classregisterer)
		{
			if (classregisterer != null)
				classregisterer.Initialize(this);
		}

		public void RegisterAllClasses()
		{
			// hitta alla laddade instanser av IClassRegistry och kör init på dessa med this som parameter

			try
			{
				Assembly[] asss = AppDomain.CurrentDomain.GetAssemblies();
				foreach (Assembly a in asss)
				{
					logger.Debug("Checking loaded assembly: " + a.FullName);
					Type[] atypes = a.GetTypes();
					foreach (Type t in atypes)
					{
						bool isclassloader = false;

						Type[] ifs = t.GetInterfaces();
						foreach (Type t2 in ifs)
						{
							if (t2 == typeof(ITypeRegistryLoader))
								isclassloader = true;
						}

						if (isclassloader)
						{
							logger.Debug("Found an type loader: " + t.FullName);
							try
							{
								ITypeRegistryLoader regger = (ITypeRegistryLoader)Activator.CreateInstance(t);
								if (regger != null)
									regger.Initialize(this);
							}
							catch (Exception z)
							{
								logger.Error("Failed to create ITypeRegistryLoader (" + t + ")", z);
							}
						}
					}
				}
			}
			catch (Exception z)
			{
				logger.Error("Failed to register classes", z);
			}
		}


		public void RegisterAll(XmlNode node)
		{
			logger.Debug("Registering from XML: " + node.InnerXml);

			if (node != null)
			{
				XmlNodeList nl1;

				nl1 = node.SelectNodes("injectattribute/register");
				foreach (XmlNode n in nl1)
				{
					string realtypename = "";
					if (n.Attributes["type"] != null)
						realtypename = n.Attributes["type"].Value;

					AddActivatorAttribute(Type.GetType(realtypename));
				}

				nl1 = node.SelectNodes("types/register");
				foreach (XmlNode n in nl1)
				{
					string realtypename = "";
					if (n.Attributes["type"] != null)
						realtypename = n.Attributes["type"].Value;

					string interfacename = "";
					if (n.Attributes["for"] != null)
						interfacename = n.Attributes["for"].Value;

					ObjectScope scope = ObjectScope.Local;
					if (n.Attributes["scope"] != null)
					{
						string _scope = n.Attributes["scope"].Value;
						if (_scope == "global" || _scope == "singleton")
							scope = ObjectScope.SharedGlobal;

					}

					try
					{
						Type realtype = Type.GetType(realtypename);
						if (realtype != null)
						{
							Type intftype = Type.GetType(interfacename);
							if (intftype != null)
							{
								Register(intftype, realtype, scope);
							}
							else
								logger.Warn("Unable to find interface: " + interfacename);
						}
						else
							logger.Warn("Unable to find type: " + realtypename);
					}
					catch (Exception z)
					{
						logger.Error("Failed to register", z);
					}


				}



				nl1 = node.SelectNodes("singletons/register");
				foreach (XmlNode n in nl1)
				{
					string realtypename = "";
					if (n.Attributes["type"] != null)
						realtypename = n.Attributes["type"].Value;

					string interfacename = "";
					if (n.Attributes["for"] != null)
						interfacename = n.Attributes["for"].Value;

					try
					{
						Type realtype = Type.GetType(realtypename);
						if (realtype != null)
						{
							Type intftype = Type.GetType(interfacename);
							if (intftype != null)
							{
								// RegisterSingleton(intftype, Activator.CreateInstance(realtype));
								RegisterSingleton(intftype, Construct(realtype));
							}
							else
								logger.Warn("Unable to find interface: " + interfacename);
						}
						else
							logger.Warn("Unable to find type: " + realtypename);
					}
					catch (Exception z)
					{
						logger.Error("Failed to register", z);
					}


				}
			}
		}

		public void InjectProperty(object target, Type targettype, string propname, object value)
		{
			PropertyInfo pi = targettype.GetProperty(propname);
			if (pi == null)
				return;

			pi.SetValue(target, value, null);
		}

		public void InjectProperty(object target, Type targettype, string propname)
		{
			PropertyInfo pi = targettype.GetProperty(propname);
			if (pi == null)
				return;


			pi.SetValue(target, Create(pi.PropertyType), null);
		}

		public void InjectField(object target, Type targettype, string propname, object value)
		{
			FieldInfo pi = targettype.GetField(propname);
			if (pi == null)
				return;

			pi.SetValue(target, value);
		}

		public void InjectField(object target, Type targettype, string propname)
		{
			FieldInfo pi = targettype.GetField(propname);
			if (pi == null)
				return;

			pi.SetValue(target, Create(pi.FieldType));
		}

		public void Inject(object target)
		{
			if (target == null)
				return;

			Type t = target.GetType();

			var pis = t.GetProperties(BindingFlags.Public | BindingFlags.Instance);
			foreach (var pi in pis)
			{
				// logger.Debug("Found property " + pi.Name + " (" + pi.PropertyType + ")");
				bool can_inject = false;

				var pias = pi.GetCustomAttributes(true);
				foreach (var pia in pias)
				{
					// logger.Debug("Has attribute " + pia);
					foreach (var activatorattribute in activator_attributes)
						if (pia.GetType().IsAssignableFrom(activatorattribute))
							can_inject = true;
				}
				if (can_inject)
				{
					object oldvalue = pi.GetValue(target, null);
					if (oldvalue == null)
					{
						// logger.Debug("Injecting into property " + pi.Name);
						object o = Create(pi.PropertyType);
						// logger.Debug("Injecting value " + o);
						pi.SetValue(target, o, null);
					}
				}
			}

			var fis = t.GetFields(BindingFlags.Public | BindingFlags.Instance);
			foreach (var fi in fis)
			{
				// logger.Debug("Found field " + fi.Name + " (" + fi.FieldType + ")");

				bool can_inject = false;
				var fias = fi.GetCustomAttributes(true);
				foreach (var fia in fias)
				{
					// logger.Debug("Has attribute " + fia);
					foreach (var activatorattribute in activator_attributes)
						if (fia.GetType().IsAssignableFrom(activatorattribute))
							can_inject = true;
				}
				if (can_inject)
				{
					object oldvalue = fi.GetValue(target);
					if (oldvalue == null)
					{
						// logger.Debug("Injecting into field " + fi.Name);
						object o = Create(fi.FieldType);
						// logger.Debug("Injecting value " + o);
						fi.SetValue(target, o);
					}
				}
			}
		}
	}
}
